package sys

import (
	"fmt"
	"github.com/vueadmin/utils"
	"github.com/vueadmin/utils/conv"
	"go/ast"
	"go/parser"
	"go/token"
	"strings"
)

func BuildFunc(buildcontent string, localfile string) {
	var (
		funcdata   []map[string]string
		structdata []map[string]string
		header     []string
		imports    []string
	)
	content := ""
	target_func, target_header, target_struct, packageName, target_imports := GetCode(buildcontent)

	if !utils.FileExists(localfile) || utils.FileGetContents(localfile) == "" {
		funcdata = []map[string]string{}
		structdata = []map[string]string{}
		header = []string{}
	} else {
		funcdata, header, structdata, _, imports = GetCode(utils.FileGetContents(localfile))
	}

	for _, v := range imports {
		if !conv.IsValueInList(v, target_imports) {
			target_imports = append(target_imports, v)
		}
	}

	if len(header) > 0 {
		for _, v := range header {
			if !conv.IsValueInList(v, target_header) && !conv.IsEmpty(v) {
				target_header = append(target_header, v)
			}
		}
	}

	target_struct_key := []string{}
	for _, v := range target_struct {
		target_struct_key = append(target_struct_key, v["structname"])
	}
	for _, v := range structdata {
		doc := GetBuildStatus(v["doc"])
		if doc == "true" {
			if conv.IsValueInList(v["structname"], target_struct_key) {
				target_struct = append(target_struct, v)
			}
		} else {
			target_struct = append(target_struct, v)
		}
	}

	target_func_key := []string{}
	for _, v := range target_func {
		target_func_key = append(target_func_key, v["funcname"])
	}
	for _, v := range funcdata {
		doc := GetBuildStatus(v["doc"])
		if doc == "true" {
			if conv.IsValueInList(v["funcname"], target_func_key) {
				target_func = append(target_func, v)
			}
		} else {
			target_func = append(target_func, v)
		}
	}

	filtered := make([]map[string]string, 0)
	seen := make(map[string]int)

	for _, item := range target_func {
		funcname := item["funcname"]
		doc := GetBuildStatus(item["doc"])
		index, exists := seen[funcname]
		if exists {
			if doc == "false" {
				filtered[index] = item
			}
		} else {
			seen[funcname] = len(filtered)
			filtered = append(filtered, item)
		}
	}

	struct_filtered := make([]map[string]string, 0)
	struct_seen := make(map[string]int)

	for _, item := range target_struct {
		funcname := item["structname"]
		doc := GetBuildStatus(item["doc"])
		index, exists := struct_seen[funcname]
		if exists {
			if doc == "false" {
				struct_filtered[index] = item
			}
		} else {
			struct_seen[funcname] = len(struct_filtered)
			struct_filtered = append(struct_filtered, item)
		}
	}

	content = "package " + packageName + "\n\n"

	if len(target_imports) > 0 {
		if len(target_imports) == 1 {
			content = content + fmt.Sprintf("import	\"%s\"", target_imports[0]) + "\n\n"
		} else {
			content += "import (\n"
			for _, v := range target_imports {
				content = content + fmt.Sprintf("	\"%s\"", v) + "\n"
			}
			content += ")\n\n"
		}
	}

	for _, v := range target_header {
		content = content + v + "\n\n"
	}

	for _, v := range struct_filtered {
		if conv.IsEmpty(v["doc"]) {
			content = content + "type " + v["structcode"] + "\n\n"
		} else {
			content = content + "/*" + v["doc"] + "*/" + "\n" + "type " + v["structcode"] + "\n\n"
		}
	}

	for _, v := range filtered {
		if conv.IsEmpty(v["doc"]) {
			content = content + v["funcode"] + "\n\n"
		} else {
			content = content + "/*" + v["doc"] + "*/" + "\n" + v["funcode"] + "\n\n"
		}
	}

	utils.FilePutContents(localfile, content)
}

func GetCode(content string) ([]map[string]string, []string, []map[string]string, string, []string) {
	fset := token.NewFileSet()
	file, err := parser.ParseFile(fset, "", content, parser.ParseComments)
	if err != nil {
		panic(err)
	}
	data := make([]map[string]string, 0) //方法
	other := make([]string, 0)
	structData := make([]map[string]string, 0) //结构体
	imports := make([]string, 0)               //导入包

	for _, imp := range file.Imports {
		imports = append(imports, strings.Trim(imp.Path.Value, `"`))
	}

	for _, decl := range file.Decls {
		switch decl := decl.(type) {
		case *ast.FuncDecl:
			var doc string
			if decl.Doc != nil {
				doc = decl.Doc.Text()
			}

			data = append(data, map[string]string{
				"funcname": decl.Name.Name,
				"funcode":  content[decl.Pos()-1 : decl.End()-1],
				"doc":      doc,
			})
		case *ast.GenDecl:
			if decl.Tok == token.TYPE {
				for _, spec := range decl.Specs {
					var doc string
					if decl.Doc != nil {
						doc = decl.Doc.Text()
					}
					if typeSpec, ok := spec.(*ast.TypeSpec); ok {
						if _, ok := typeSpec.Type.(*ast.StructType); ok {
							structData = append(structData, map[string]string{
								"structname": typeSpec.Name.Name,
								"structcode": content[typeSpec.Pos()-1 : typeSpec.End()-1],
								"doc":        doc,
							})
						}
					}
				}
			}

			if decl.Tok == token.VAR {
				for _, spec := range decl.Specs {
					if _, ok := spec.(*ast.ValueSpec); ok {
						start := decl.Pos()
						end := decl.End()
						declContent := content[start-1 : end]
						declContent = strings.Replace(declContent, "\r", "", -1)
						declContent = strings.Replace(declContent, "\n", "", -1)
						other = append(other, declContent)
					}
				}
			}

		}
	}

	packageName := file.Name.Name
	return data, other, structData, packageName, imports
}

func GetBuildStatus(doc string) string {
	if len(strings.Split(doc, "@buildcode(")) > 1 {
		data := strings.Split(strings.Split(doc, "@buildcode(")[1], ")")
		return strings.Trim(data[0], "")
	} else {
		return "false"
	}
}
